package com.pyb.common.utils;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.pyb.common.exception.BizException;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.fibers.httpasyncclient.FiberCloseableHttpAsyncClient;
import co.paralleluniverse.strands.SuspendableCallable;

/**
 * FiberHttpUtil
 *
 * @author chuchen.zhang
 * @date 17-2-27 下午11:49
 */
public class FiberHttpUtil {

	private static final Logger logger = LoggerFactory.getLogger(FiberHttpUtil.class);
	private static final int SOCKET_TIMEOUT = 3000;
	private static final int CONNECT_TIMEOUT = 5000;
	private static final int MAX_CONN_PER_ROUTE = 1000;
	private static final int MAX_CONN_TOTAL = 5000;

	private static final CloseableHttpAsyncClient client;

	static {
		RequestConfig requestConfig
				= RequestConfig.custom()
				.setSocketTimeout(SOCKET_TIMEOUT)
				.setConnectTimeout(CONNECT_TIMEOUT)
				.build();
		client = FiberCloseableHttpAsyncClient.wrap(
				HttpAsyncClients.custom()
				.setMaxConnPerRoute(MAX_CONN_PER_ROUTE)
				.setMaxConnTotal(MAX_CONN_TOTAL)
				.setDefaultRequestConfig(requestConfig)
				.build()
		);
		client.start();
	}


	public static String get(String url) {
		try {
			HttpGet request = new HttpGet(url);
			return execute(request, SOCKET_TIMEOUT, TimeUnit.MILLISECONDS);
		} catch (Exception e) {
			logger.error("http get error", e);
			return null;
		}
	}

	public static String get(String url, int timeOut, TimeUnit timeUnit) {
		try {
			HttpGet request = new HttpGet(url);
			return execute(request, timeOut, timeUnit);
		} catch (Exception e) {
			logger.error("http get error", e);
			return null;
		}
	}

	public static String post(String url, Map<String, String> params, int timeOut, TimeUnit timeUnit) {
		try {
			HttpPost request = new HttpPost(url);
			List<NameValuePair> urlParameters = Lists.newArrayList();
			for (Map.Entry<String, String> paramEntity : params.entrySet()) {
				urlParameters.add(new BasicNameValuePair(paramEntity.getKey(), paramEntity.getValue()));
			}
			request.setEntity(new UrlEncodedFormEntity(urlParameters));
			return execute(request, timeOut, timeUnit);
		} catch (Exception e) {
			logger.error("http post error", e);
			return null;
		}
	}

	public static String post(String url, String content, Charset charset) {
		return post(url, content, charset, SOCKET_TIMEOUT, TimeUnit.MILLISECONDS);
	}

	public static String post(String url, String content, Charset charset, int timeOut, TimeUnit timeUnit) {
		try {
			HttpPost request = new HttpPost(url);
			request.setEntity(new StringEntity(content, charset));
			return execute(request, timeOut, timeUnit);
		} catch (Exception e) {
			logger.error("http post error", e);
			return null;
		}
	}

	public static String execute(final HttpUriRequest request, final int timeOut, final TimeUnit timeUnit) {
		Preconditions.checkNotNull(request, "input param con't be null");
		final SuspendableCallable<String> call = new SuspendableCallable<String>() {
			@Override
			public String run() throws SuspendExecution, InterruptedException {
				try {
					Future<HttpResponse> future = client.execute(request, emptyCallback);
					HttpResponse response = future.get(timeOut, timeUnit);
					int statusCode = response.getStatusLine().getStatusCode();
					if (statusCode == 200) {
						return EntityUtils.toString(response.getEntity()).trim();
					}
				}  catch (ExecutionException | IOException | TimeoutException e) {
					logger.error("fiber http error");
					throw new BizException(e, "fiber http error");
				}
				return null;
			}
		};
		return FiberUtil.execute(call);
	}

	private static final FutureCallback<HttpResponse> emptyCallback = new FutureCallback<HttpResponse>() {
		@Override
		public void completed(HttpResponse httpResponse) {
			// do nothing
		}

		@Override
		public void failed(Exception e) {
			// do nothing
		}

		@Override
		public void cancelled() {
			// do nothing
		}
	};

}
